/*
 * Proto Streamer
 * Copyright (C) 2009 John Pritchard.
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License as
 * published by the Free Software Foundation; either version 2 of the
 * License, or (at your option) any later version.
 * 
 * This program is distributed in the hope that it will be useful, but
 * WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
 * 02110-1301 USA.
 */
package p.s;

import java.io.File;
import java.io.IOException;
import java.io.PrintStream;
import java.net.InetSocketAddress;
import java.net.ServerSocket;
import java.net.Socket;
import java.net.URISyntaxException;
import java.nio.channels.ServerSocketChannel;

/**
 * @author jdp
 */
public class Server
    extends Thread
{
    private final static int Depth = 20;

    public final static String Header = "Server: "+Version.Full;

    protected final static long Seconds = 1000L;
    protected final static int Sot = (int)(15L * Seconds);


    private final String pwd;
    private ServerSocket socket;
    private InetSocketAddress port;


    public Server(InetSocketAddress port)
        throws IOException
    {
        super(Group.Top,("Proto-Server:"+port));
        this.pwd = new Welcome().getAbsolutePath();
        this.port = port;
        this.socket = ServerSocketChannel.open().socket();
        this.socket.bind(port,Server.Depth);
        {
            Shutdown shutdown = new Shutdown(this);
            shutdown.install();
        }
    }


    public final int getPort(){
        return this.port.getPort();
    }
    public final boolean isDefaultPort(Protocol p){
        return (p.isDefaultPort(this));
    }
    public final boolean isNotDefaultPort(Protocol p){
        return (p.isNotDefaultPort(this));
    }
    public final boolean isLegal(java.io.File reference){

        return reference.getAbsolutePath().startsWith(this.pwd);
    }
    public final boolean isNotLegal(java.io.File reference){

        return (!reference.getAbsolutePath().startsWith(this.pwd));
    }
    public void log(Request q, Response p){

        PrintStream log = System.err;
        synchronized(log){
            q.head(log);
            p.head(log);
        }
    }
    public void log(Request q, Throwable t){

        PrintStream log = System.err;
        synchronized(log){
            log.print(q.getName()+' ');
            t.printStackTrace(log);
            log.println();
        }
    }
    protected String id(String cli, long id){
        return cli+' '+Long.toHexString(id);
    }
    void shutdown(){

        this.stop();
    }
    protected Request createRequest(Socket client)
        throws IOException, URISyntaxException
    {

        return new Request(this,client);
    }
    protected void serverBegin(){
    }
    protected void serverHalt(){
    }
    public final void run(){
        this.serverBegin();
        {
            Init.Service service = new Init.Service();
            service.init(this);
        }
        ServerSocket socket = this.socket;
        if (null != socket){
            try {
                Socket client = null;
                while (true){
                    try {
                        client = socket.accept();
                        client.setSoTimeout(Sot);
                        {
                            Request request = this.createRequest(client);
                            request.start();
                        }
                        client = null;
                    }
                    catch (IOException ignore){
                        if (null != client){
                            try {
                                client.close();
                            }
                            catch (IOException iox){
                            }
                        }
                    }
                    catch (Throwable other){
                        if (null != client){
                            try {
                                client.close();
                            }
                            catch (IOException iox){
                            }
                        }
                        System.gc();
                        Thread.yield();
                    }
                }
            }
            finally {
                try {
                    socket.close();
                }
                catch (IOException ignore){
                }
                finally {
                    this.serverHalt();
                }
            }
        }
    }

    public final static void usage(PrintStream out){
        out.println();
        out.println("Usage");
        out.println();
        out.println("    p.s.Server [-h localhost] [-p portnum] ");
        out.println();
        out.println("Arguments");
        out.println();
        out.println("    The host argument permits listener interface binding. ");
        out.println();
        out.println("    The port number defaults to '80'. ");
        out.println();
        out.println("Files");
        out.println();
        out.println("    Files are served from '(pwd)/(Host)/(Path)' locations");
        out.println("    under the servers present working directory (pwd).");
        out.println();
        out.println("    The server's pwd contains the main jar file and the 'lib'");
        out.println("    subdirectory.");
        out.println();
        out.println("Applications");
        out.println();
        out.println("    The server's builtin classpath includes '(pwd)/classes' for ");
        out.println("    application classes.  However, any other application jar files");
        out.println("    would need to be added to the java runtime classpath.");
        out.println();
        out.println("    Applications define HTTP Methods as subclasses of ");
        out.println("      p.s.Response");
        out.println("    packaged and named for host and path and method");
        out.println("    according to the following procedure.");
        out.println();
        out.println("    The request path identifies a package, with '/' converted to");
        out.println("    '.'.  The top level request path '/' has no application ");
        out.println("    methods.");
        out.println();
        out.println("    The request method is appended after conversion to camel ");
        out.println("    case, as in 'ABC' becomes 'Abc'.");
        out.println();
        out.println("    So for example, the following HTTP request");
        out.println("        GET /foo HTTP/1.0");
        out.println("    may be handled by a subclass of 'p.s.Response' identified as");
        out.println("        foo.Get");
        out.println();

        System.exit(1);
    }
    public final static void main(String[] argv){
        String hostname = null;
        int portnum = 80;
        for (int cc = 0, count = argv.length; cc < count; cc++){
            String arg = argv[cc];
            if ('-' == arg.charAt(0)){
                switch (arg.charAt(1)){
                case 'h':
                    hostname = argv[++cc];
                    break;
                case 'p':
                    portnum = Integer.parseInt(argv[++cc]);
                    break;
                default:
                    usage(System.err);
                }
            }
            else {
                usage(System.err);
            }
        }
        InetSocketAddress inf;
        if (null != hostname)
            inf = new InetSocketAddress(hostname,portnum);
        else
            inf = new InetSocketAddress(portnum);
        try {
            Server server = new Server(inf);
            server.start();
        }
        catch (IOException exc){
            exc.printStackTrace();
            System.exit(1);
        }
    }
}
